#include "sysx/System.h"
#include "mustache.hpp"
#include <string>
#include <locale>
#include <codecvt> 
#include <iomanip>
#include <windows.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <iostream>
#include <set>
#include <locale>
#include <thread>
#include <codecvt>

#undef min
#undef max
#include <msdfgen/msdfgen.h>
#include <msdfgen/msdfgen-ext.h>
#include <gli/gli.hpp>

#include "fxcc/core/Dir.h"
#include "fxcc/core/AssertManager.h"
#include "fxcc/core/graph/common/ImageMipmap.h"
#include "fxcc/core/graph/common/sdf/SDFCharMap.h"
#include "fxcc/core/graph/common/sdf/SDFInfo.h"
#include "fxcc/core/graph/common/gliUtils.h"

#include "fxcc/bin/Serializer.h"

using namespace fxcc::bin;

using namespace msdfgen;

struct SDFFontFile :public fxcc::Base
{
    std::string m_Path;
    // key is glyph 
    SDFCharMap m_SDFCharMap;

    // Altas Image path
    SDFInfo m_SDFInfo;

    // SDFDir
    std::string m_SDFDir;

    // wcharset
    msdfgen::Shape* shapePtr;

    // default font Size
    int m_FontSize = 90;

    // sdf range size translate
    float m_SymmetricalWidth = 0.125f;
    glm::ivec2 m_Msdf_Size = { 64,64 };

    // Font Family
    std::string m_Family;
    std::string m_FileName;


    // FT_File
    FT_Face m_Face;
    FT_Library m_Library;

    SDFFontFile(const std::string& path) :m_Path(path) {

        m_Available = Init();
    };

    bool AtlasPackage()
    {
        const auto& m_SDFChars = m_SDFCharMap.m_SDFChars;

        if (m_SDFChars.empty())
        {
            return false;
        }

        const int NumColumn = 4;

        for (const auto& [key, value] : m_SDFChars)
        {
            int charCode = value.m_Charcode;
            fs::path sdfPath = fs::path(m_SDFDir) / fs::path(m_FileName) / (GetMSDFFileName(value.m_WChar));

            if (!fs::exists(sdfPath))
            {
                continue;
            }
            ztclog::info("get bmp %s", sdfPath.string().c_str());

        }
        return true;

    }
    bool Init()
    {
        shapePtr = new Shape();;
        m_FileName = fxcc::Base::GetNoSuffixName(fs::path(m_Path).filename().string());

        ztclog::info("font filename is %s", m_FileName.c_str());

        if (!fs::exists(m_Path))
        {
            ztclog::info("Failed find file %s", m_Path.c_str());
            return false;
        }
        if (FT_Init_FreeType(&m_Library))
        {
            ztclog::info("cannot init freetype ");
            return false;
        }
        if (FT_New_Face(m_Library, m_Path.c_str(), 0, &m_Face))
        {
            ztclog::info("cannot freetype new face ");
            return false;
        }
        m_Family = m_Face->family_name;
        ztclog::info("font family is %s", m_Family.c_str());

        LoadAllChars();

        return true;
    };

    static std::string MSDF_Cfg_Name()
    {
        return "msdf.config";
    }
    bool Altas_MSDF_Save_Info()
    {
        fxcc::yml::Serializer ymlser;
        fs::path _p = fs::path(RealDir()) / MSDF_Cfg_Name();
        return ymlser.EncodePath<SDFInfo>(_p.string(), m_SDFInfo);
    };

    bool Altas_MSDF_Save_Char_map()
    {
        fxcc::bin::Serializer binser;
        fs::path _char_map_path = fs::path(RealDir()) / fs::path(CharMapBinFileName());
        binser.WritePath<SDFCharMap>(_char_map_path.string(), m_SDFCharMap);
        return true;
    }
    static std::string CharMapBinFileName()
    {
        return "char.map";
    }

    bool Altas_MSDF_bmp_all()
    {
        auto& info = m_SDFInfo;
        auto& m_SDFChars = m_SDFCharMap.m_SDFChars;
        glm::ivec2 m_SubImageSize = info.m_ImagePageSize;

        info.m_FontFamily = m_Family;
        info.m_FileName = m_FileName;
        info.m_NumChars = m_SDFChars.size();
        info.m_NumCols = m_SubImageSize.x / m_Msdf_Size.x;
        info.m_NumRows = m_SubImageSize.y / m_Msdf_Size.y;
        info.m_NumPages = info.m_NumChars / (info.m_NumRows * info.m_NumCols) + 1;
        info.m_NumCharsOfPage = info.m_NumRows * info.m_NumCols;
        info.m_LeftCharsOfPage = info.m_NumChars % info.m_NumCharsOfPage;
        info.m_FontSize = m_FontSize;

        std::vector<fxcc::graph::common::ImageMipmap::MipMapData> m_Images;
        m_Images.resize(info.m_NumPages);

        for (auto& image : m_Images)
        {
            image.m_Width = m_SubImageSize.x;
            image.m_Height = m_SubImageSize.y;
            image.m_NrComponent = 3;
            image.m_HDR = false;
            image.InitData();
        }

        int charIndex = 0;

        for (auto& [key, value] : m_SDFChars)
        {
            value.m_PageIndex = (charIndex) / info.m_NumCharsOfPage;
            value.m_RealIndex = (charIndex) % info.m_NumCharsOfPage;

            auto& targetImage = m_Images[value.m_PageIndex];

            fs::path sdfPath = fs::path(RealDir()) / (GetMSDFFileName(key));

            const int numCols = info.m_NumCols;
            const int numRows = info.m_NumCols;

            int colIndex = value.m_RealIndex % numCols;
            int rowIndex = value.m_RealIndex / numCols;

            fxcc::graph::common::ImageMipmap::Desc msdfImageDesc;
            msdfImageDesc.m_WICFlipY = false;
            msdfImageDesc.m_wicHdr = false;
            msdfImageDesc.m_wicReq = 3;
            msdfImageDesc.m_wicPath = sdfPath.string();
            fxcc::graph::common::ImageMipmap msdfImage(msdfImageDesc);

            value.m_TexGridIndex = glm::ivec2(colIndex, rowIndex);
            value.m_TexGridNum = glm::ivec2(numCols, numRows);

            targetImage.DrawRect(msdfImage.m_WICTexture, value.m_TexGridNum, value.m_TexGridIndex);

            charIndex++;
        }

        for (int i = 0; i < m_Images.size(); i++) {

            fs::path _dir(RealDir());
            fs::path _sub_image_path = _dir / fs::path(std::to_string(i) + PageFileSuffix());
            if (!fs::exists(_dir))
            {
                fs::create_directories(_dir);
            }
            m_Images[i].SaveBMP(_sub_image_path.string());
        }
        return true;
    }

    static std::string PageFileSuffix() {
        return "_.bmp";
    }
    bool LoadWstring(const std::wstring& text)
    {
        std::set<unsigned long> m_Charset;

        for (const unsigned long& ch : text)
        {
            m_Charset.insert(ch);
        }

        FT_Set_Pixel_Sizes(m_Face, 0, m_FontSize);

        for (const unsigned long& ch : m_Charset)
        {
            LoadWChar(ch);
        }
        return true;
    };

    bool LoadAllChars()
    {
        FT_Set_Pixel_Sizes(m_Face, 0, m_FontSize);

        FT_ULong charcode;
        FT_UInt glyph_index;

        charcode = FT_Get_First_Char(m_Face, &glyph_index);
        while (glyph_index != 0)
        {
            if (FT_Load_Glyph(m_Face, glyph_index, FT_LOAD_RENDER) == 0)
            {
                ztclog::info("Loaded glyph for charcode: %d", static_cast<int>(charcode));
                LoadWChar(charcode);
            }
            charcode = FT_Get_Next_Char(m_Face, charcode, &glyph_index);
        }
        return true;
    }
    bool LoadWChar(const unsigned long ch)
    {

        // Load character glyph
        FT_UInt glyph_index = FT_Get_Char_Index(m_Face, ch);
        if (FT_Load_Glyph(m_Face, glyph_index, FT_LOAD_RENDER))
        {
            return false;
        }
        auto& m_SDFChars = m_SDFCharMap.m_SDFChars;

        SDFChar& sdfChar = m_SDFChars[GetCharcode(ch)];
        sdfChar.m_Size = glm::vec2(m_Face->glyph->bitmap.width, m_Face->glyph->bitmap.rows);
        sdfChar.m_Bearing = glm::vec2(m_Face->glyph->bitmap_left, m_Face->glyph->bitmap_top);
        sdfChar.m_Advance = glm::vec2(m_Face->glyph->advance.x, m_Face->glyph->advance.y);
        sdfChar.m_WChar = ch;
        sdfChar.m_Charcode = GetCharcode(ch);

        return true;
    }

    int GetCharcode(const unsigned long& ch)
    {
        int charCode = (static_cast<int>(ch));
        return charCode;
    }
    std::string GetMSDFFileName(const unsigned long& ch) const
    {
        int charCode = (static_cast<int>(ch));
        std::string fileName = std::to_string(charCode) + ".bmp";
        return fileName;

    };

    std::string RealDir() const
    {
        fs::path _p = fs::path(m_SDFDir) / fs::path(m_FileName);

        return _p.string();
    }

    bool Save_MSDF_bmp_all() const
    {
        return Save_MSDF_bmp(m_SDFCharMap.m_SDFChars);
    }
    bool Remove_MSDF_bmp_all() const
    {
        for (const auto& [key, value] : m_SDFCharMap.m_SDFChars)
        {
            unsigned long charCode = key;
            std::string charStr = std::to_string(charCode);

            fs::path sdfPath = fs::path(RealDir()) / (GetMSDFFileName(charCode));

            if (fs::exists(sdfPath))
            {
                fs::remove(sdfPath);
            }
        }
        return true;
    }
    bool Save_MSDF_bmp(const std::map<unsigned long, SDFChar>& chars) const
    {
        const std::string fontPath = m_Path;

        if (!fs::exists(RealDir()))
        {
            fs::create_directories(RealDir());
        }

        auto& shape = *shapePtr;

        if (FreetypeHandle* ft = initializeFreetype()) {
            if (FontHandle* font = loadFont(ft, fontPath.c_str())) {

                for (const auto& [key, value] : chars)
                {
                    unsigned long charCode = key;
                    std::string charStr = std::to_string(charCode);

                    fs::path sdfPath = fs::path(RealDir()) / (GetMSDFFileName(charCode));

                    if (loadGlyph(shape, font, charCode, FONT_SCALING_EM_NORMALIZED)) {
                        shape.normalize();
                        //                      max. angle
                        edgeColoringSimple(shape, 3.0);
                        //          output width, height
                        Bitmap<float, 3> msdf({ m_Msdf_Size.x,m_Msdf_Size.y });

                        glm::vec2 m_Msdf_Translate{ 0.125, 0.125 };

                        msdfgen::Shape::Bounds bounds = shape.getBounds();

                        SDFTransformation t(Projection({ (double)m_Msdf_Size.x,(double)m_Msdf_Size.y }, { m_Msdf_Translate.x, m_Msdf_Translate.y }), Range(m_SymmetricalWidth));

                        generateMSDF(msdf, shape, t);

                        if (!fs::exists(sdfPath))
                        {
                            ztclog::info("save path %s", sdfPath.string().c_str());
                            saveBmp(msdf, sdfPath.string().c_str());
                        }
                    }
                }

                destroyFont(font);
            }
            deinitializeFreetype(ft);
        }
        return true;
    }
    ~SDFFontFile()
    {

        FT_Done_Face(m_Face);
        FT_Done_FreeType(m_Library);
    }
};


int main()
{
    ztclog::pushFunction([](const ztclog::Line& l) {std::cout << l.m_Text << std::endl; });
    
    std::vector<std::shared_ptr<std::thread>> m_Threads;

    std::shared_ptr<std::thread> th;
    
    fs::path _fontDir = "C:/Windows/Fonts";

    for (const auto& it : fs::directory_iterator(_fontDir))
    {
        std::string _fontPath = it.path().string();
        std::string suffix = fxcc::Base::GetSuffix(_fontPath);
        
        if (fs::is_directory(it.path()))
        {
            continue;
        }
        if (!fxcc::FileUtil::IsFontFile(suffix))
        {
            continue;
        }

        std::string fileNameNoSuffix = fxcc::Base::GetNoSuffixName(it.path().filename().string());
        "SimsunExtG.ttf";
        "simfang.ttf";
        "simkai.";

        //if (fileNameNoSuffix == "arial")
        if (fileNameNoSuffix == "arial")
        {
            auto func = [=]() {

                std::shared_ptr< SDFFontFile> sdfFont = std::make_shared<SDFFontFile>(_fontPath);
                sdfFont->m_SDFDir = fxcc::AssertManager::GetRelativeFile("sdf_out2");
                sdfFont->m_SDFInfo.m_ImagePageSize = { 4096,4096 };
                //sdfFont->m_Msdf_Size = { 64,64 };
                //sdfFont->m_Msdf_Translate = { 32,32 };

                sdfFont->Save_MSDF_bmp_all();
                sdfFont->Altas_MSDF_bmp_all();
                sdfFont->Remove_MSDF_bmp_all();
                sdfFont->Altas_MSDF_Save_Char_map();
                sdfFont->Altas_MSDF_Save_Info();


                };

            auto th = std::make_shared<std::thread>(func);
            m_Threads.push_back(th);
        }

    }
    for (auto th : m_Threads)
    {
        th->join();
    }

    return 0;
}